package cn.iocoder.yudao.module.call.framework.client.impl.cm;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.call.api.client.dto.*;
import cn.iocoder.yudao.module.call.dal.dataobject.cmagent.CmAgentDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmacstask.CmAcsTaskDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmacstask.CmAcsTaskOutNumberDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmacstask.CmAcsTaskPhoneDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmacstask.CmAcsTimehourDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmrecord.CmAcsDiallogDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmrecord.CmCalloutDetailDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmrecord.CmRecordFileDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmrecord.CmRecordPushDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmskill.CmAgentServiceSkillDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmskill.CmServiceSkillDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmsubscriber.CmSubscriberDO;
import cn.iocoder.yudao.module.call.dal.dataobject.cmsubscriber.KmSubscriberDO;
import cn.iocoder.yudao.module.call.dal.mysql.cmrecordfile.CmAcsDiallogMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmacstask.CmAcsTaskMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmacstask.CmAcsTaskOutNumberMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmacstask.CmAcsTaskPhoneMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmacstask.CmAcsTimehourMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmagent.CmAgentMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmrecordfile.CmCalloutDetailMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmrecordfile.CmRecordFileMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmrecordfile.CmRecordPushMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmskill.CmAgentServiceSkillMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmskill.CmServiceSkillMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmsubscriber.CmSubscriberMapper;
import cn.iocoder.yudao.module.call.dal.mysql.cmsubscriber.KmSubscriberMapper;
import cn.iocoder.yudao.module.call.enums.*;
import cn.iocoder.yudao.module.call.framework.client.CallCenter;
import cn.iocoder.yudao.module.call.api.client.dto.task.CreateCallTaskDTO;
import cn.iocoder.yudao.module.call.framework.client.impl.AbstractCallClient;
import cn.iocoder.yudao.module.call.framework.client.property.CallProperties;
import cn.iocoder.yudao.module.call.service.cmacstaskphone.CmAcsTaskPhoneService;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;


import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import cn.iocoder.yudao.module.call.service.cmacstaskphone.CmAcsTaskPhoneService;

import javax.sql.DataSource;

import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;

/**
 * 所有处理针对cm的库,固定tenant_id为cmTenantId
 */
@Slf4j
public class CmClient extends AbstractCallClient implements CallCenter {

    private CmAgentMapper cmAgentMapper;
    private CmSubscriberMapper cmSubscriberMapper;
    private KmSubscriberMapper kmSubscriberMapper;
    private CmServiceSkillMapper cmServiceSkillMapper;
    private CmAgentServiceSkillMapper cmAgentServiceSkillMapper;
    private CmAcsTimehourMapper cmAcsTimehourMapper;

    private CmAcsTaskMapper cmAcsTaskMapper;
    private CmAcsTaskOutNumberMapper cmAcsTaskOutNumberMapper;
    private CmAcsTaskPhoneService cmAcsTaskPhoneService;
    private CmAcsDiallogMapper cmAcsDiallogMapper;
    private CmCalloutDetailMapper cmCalloutDetailMapper;
    private CmRecordFileMapper cmRecordFileMapper;
    private CmRecordPushMapper cmRecordPushedMapper;

    private StringRedisTemplate stringRedisTemplate;

    /**
     * 共用一个cm的TenantId, 方便管理sip-gateway和外呼号码
     */
    @Value("${call.cm.tenant-id}")
    private Long cmTenantId;
    @Value("${call.cm.domain}")
    private String cmDomain;

    @Value("${call.cm.url}")
    private String url;

    @Value("${call.cm.agent}")
    private String ws;

    @Value("${call.cm.webrtc}")
    private String webrtc;

    @Value("${call.cm.api-key}")
    private String apiKey;

    @Value("${call.cm.record-domain}")
    private String recordDomain;
    /**
     * 默认ivr流程id
     */
    @Value("${call.cm.acs-flow-id}")
    private Integer acsFlowId;

    /**
     * 默认ivr流程id
     */
    @Value("${call.cm.ivr-flow-id}")
    private Integer ivrFlowId;

//    public

    @Override
    public CallRespDTO handleCall(CallReqDTO callReqDTO) {
        return CallRespDTO.ofOk();
    }

    public CmClient(CallProperties properties) {
        super(properties);
        this.cmAgentMapper = SpringUtil.getBean(CmAgentMapper.class);
        this.cmSubscriberMapper = SpringUtil.getBean(CmSubscriberMapper.class);
        this.kmSubscriberMapper = SpringUtil.getBean(KmSubscriberMapper.class);
        this.cmServiceSkillMapper = SpringUtil.getBean(CmServiceSkillMapper.class);
        this.cmAgentServiceSkillMapper = SpringUtil.getBean(CmAgentServiceSkillMapper.class);
        this.cmAcsTaskMapper = SpringUtil.getBean(CmAcsTaskMapper.class);
        this.cmAcsTimehourMapper = SpringUtil.getBean(CmAcsTimehourMapper.class);
        this.cmAcsTaskOutNumberMapper = SpringUtil.getBean(CmAcsTaskOutNumberMapper.class);
        this.cmAcsTaskPhoneService = SpringUtil.getBean(CmAcsTaskPhoneService.class);
        this.cmAcsDiallogMapper = SpringUtil.getBean(CmAcsDiallogMapper.class);
        this.cmCalloutDetailMapper = SpringUtil.getBean(CmCalloutDetailMapper.class);
        this.cmRecordFileMapper = SpringUtil.getBean(CmRecordFileMapper.class);
        this.cmRecordPushedMapper = SpringUtil.getBean(CmRecordPushMapper.class);
        this.stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);

        this.cmDomain = SpringUtil.getProperty("call.cm.domain");
        this.url = SpringUtil.getProperty("call.cm.url");
        this.acsFlowId = NumberUtil.parseInt(SpringUtil.getProperty("call.cm.acs-flow-id"));
        this.ivrFlowId = NumberUtil.parseInt(SpringUtil.getProperty("call.cm.ivr-flow-id"));
        this.ws = SpringUtil.getProperty("call.cm.agent");
        this.webrtc = SpringUtil.getProperty("call.cm.webrtc");
        this.apiKey = SpringUtil.getProperty("call.cm.api-key");
        this.recordDomain = SpringUtil.getProperty("call.cm.record-domain");
        this.cmTenantId = NumberUtil.parseLong(SpringUtil.getProperty("call.cm.tenant-id"));
    }

    @Override
    protected void doInit() {

    }

    /**
     * 创建坐席
     */
    @Override
    @DSTransactional
    public boolean createAgent(AgentReqDTO agentReqDTO) {
        return TenantUtils.execute(this.cmTenantId, () -> {
            CmAgentDO agentExist = cmAgentMapper.selectOne(CmAgentDO::getAgentid, agentReqDTO.getAgent());
            if (Objects.nonNull(agentExist)) {
                //已存在直接跳过
                return true;
            }
            //创建坐席
            CmAgentDO cmAgentDO = new CmAgentDO();
            cmAgentDO.setName(agentReqDTO.getAgent());
            cmAgentDO.setAgentid(agentReqDTO.getAgent());
            cmAgentDO.setPassword(agentReqDTO.getPassword());
            cmAgentMapper.insert(cmAgentDO);
            //创建分机
            CmSubscriberDO cmSubscriberDO = new CmSubscriberDO();
            cmSubscriberDO.setUsername(agentReqDTO.getAgent());
            cmSubscriberDO.setPassword(agentReqDTO.getPassword());
            cmSubscriberDO.setDomain(cmDomain);
            cmSubscriberMapper.insert(cmSubscriberDO);
            //创建kamailio 分机
            KmSubscriberDO kmSubscriberDO = new KmSubscriberDO();
            kmSubscriberDO.setUsername(agentReqDTO.getAgent());
            kmSubscriberDO.setPassword(agentReqDTO.getPassword());
            kmSubscriberDO.setDomain(cmDomain);
            kmSubscriberMapper.insert(kmSubscriberDO);
            //创建技能组绑定关系
            if (Objects.nonNull(agentReqDTO.getGroup1())) {
                CmAgentServiceSkillDO cmAgentServiceSkillDO1 = new CmAgentServiceSkillDO();
                CmServiceSkillDO skillDO1 = cmServiceSkillMapper.selectById(NumberUtils.parseLong(agentReqDTO.getGroup1()));
                cmAgentServiceSkillDO1.setAgentid(agentReqDTO.getAgent());
                cmAgentServiceSkillDO1.setSkillId(skillDO1.getSkillId());
                cmAgentServiceSkillDO1.setPriorityAgent(agentReqDTO.getPriorityAgent()); //坐席优先级
                cmAgentServiceSkillMapper.insert(cmAgentServiceSkillDO1);
            }
            if (Objects.nonNull(agentReqDTO.getGroup2())) {
                CmAgentServiceSkillDO cmAgentServiceSkillDO2 = new CmAgentServiceSkillDO();
                CmServiceSkillDO skillDO2 = cmServiceSkillMapper.selectById(NumberUtils.parseLong(agentReqDTO.getGroup2()));
                cmAgentServiceSkillDO2.setAgentid(agentReqDTO.getAgent());
                cmAgentServiceSkillDO2.setSkillId(skillDO2.getSkillId());
                cmAgentServiceSkillDO2.setPriorityAgent(agentReqDTO.getPriorityAgent());//坐席优先级
                cmAgentServiceSkillMapper.insert(cmAgentServiceSkillDO2);
            }
            return true;
        });

    }

    /**
     * 更新坐席
     *
     * @return
     */
    @Override
    @DSTransactional
    public void updateAgent(AgentReqDTO agentReqDTO) {
        TenantUtils.execute(this.cmTenantId, () -> {
            cmAgentServiceSkillMapper.delete(CmAgentServiceSkillDO::getAgentid, agentReqDTO.getAgent());
            //创建技能组绑定关系
            if (Objects.nonNull(agentReqDTO.getGroup1())) {
                CmAgentServiceSkillDO cmAgentServiceSkillDO1 = new CmAgentServiceSkillDO();
                CmServiceSkillDO skillDO1 = cmServiceSkillMapper.selectById(NumberUtils.parseLong(agentReqDTO.getGroup1()));
                cmAgentServiceSkillDO1.setAgentid(agentReqDTO.getAgent());
                cmAgentServiceSkillDO1.setSkillId(skillDO1.getSkillId());
                cmAgentServiceSkillDO1.setPriorityAgent(agentReqDTO.getPriorityAgent()); //坐席优先级
                cmAgentServiceSkillMapper.insert(cmAgentServiceSkillDO1);
            }
            if (Objects.nonNull(agentReqDTO.getGroup2())) {
                CmAgentServiceSkillDO cmAgentServiceSkillDO2 = new CmAgentServiceSkillDO();
                CmServiceSkillDO skillDO2 = cmServiceSkillMapper.selectById(NumberUtils.parseLong(agentReqDTO.getGroup2()));
                cmAgentServiceSkillDO2.setAgentid(agentReqDTO.getAgent());
                cmAgentServiceSkillDO2.setSkillId(skillDO2.getSkillId());
                cmAgentServiceSkillDO2.setPriorityAgent(agentReqDTO.getPriorityAgent());//坐席优先级
                cmAgentServiceSkillMapper.insert(cmAgentServiceSkillDO2);
            }
        });
    }


    /**
     * 创建技能组
     *
     * @param agentGroupReqDTO
     * @return skillId
     */
    @Override
    public String addAgentGroup(AgentGroupReqDTO agentGroupReqDTO) {
        return TenantUtils.execute(this.cmTenantId, () -> {
            //做个重复校验
            CmServiceSkillDO existSkillDO = cmServiceSkillMapper.selectOne(CmServiceSkillDO::getName, agentGroupReqDTO.getGroupName());
            if (existSkillDO != null) {
                return existSkillDO.getId().toString();
            }
            //自己生成skillId
            CmServiceSkillDO cmServiceSkillDO = new CmServiceSkillDO();
            cmServiceSkillDO.setSkillId(getSkillId());
            cmServiceSkillDO.setName(agentGroupReqDTO.getGroupName());
            cmServiceSkillDO.setQueueType(CmSkillQueueTypeEnum.getCodeByOkccCode(agentGroupReqDTO.getCallDistribution()));
            cmServiceSkillMapper.insert(cmServiceSkillDO);
            return cmServiceSkillDO.getId().toString();
        });
    }

    /**
     * 更新技能组(班组)
     */
    @Override
    public void updateAgentGroup(AgentGroupReqDTO agentGroupReqDTO) {
        TenantUtils.execute(this.cmTenantId, () -> {
            cmServiceSkillMapper.update(new LambdaUpdateWrapper<CmServiceSkillDO>().set(CmServiceSkillDO::getName, agentGroupReqDTO.getGroupName()).set(CmServiceSkillDO::getQueueType, CmSkillQueueTypeEnum.getCodeByOkccCode(agentGroupReqDTO.getCallDistribution())).eq(CmServiceSkillDO::getSkillId, NumberUtils.parseLong(agentGroupReqDTO.getThirdPartyId())));
        });
    }


    /**
     * 创建外呼任务
     */
    @Override
    public String createBatchCallTask(CreateCallTaskDTO data) {
        return TenantUtils.execute(this.cmTenantId, () -> {

            String taskId = "";
            try {
                LocalDateTime now = LocalDateTime.now();
                CmAcsTaskDO acsTaskDO = CmAcsTaskDO
                        .builder()
                        .name(data.getName())
                        .acsFlow(getCmFlowId(data.getType()))
                        .maxnum(data.getMaxNum())
                        .maxDialtimes(1) //呼叫次数 也就是重呼次数+1
                        .status(CmTaskStatusEnum.STOP.getStatus()) //默认为准备状态
                        .taskType(1101) //先固定为固定时间段每天循环：1101
                        .idleMultiple(data.getMultiplier())
                        .ctime(now)
                        .outdialType(CmTaskTypeEnum.getCmTypeByType(data.getType())) // 8000:转坐席，8001：转IVR 8002 智能外呼
                        .outdialValue(data.getThirdPartyGroupId())
                        .dialInterval(10)
                        .voiceName(data.getTimbreId()) //音色
                        .build();
                if (data.getAutoRecall().equals(0)) {
                    //需要失败重呼
                    acsTaskDO.setMaxDialtimes(data.getRecallTime() + 1); //呼叫次数 也就是重呼次数+1
                    acsTaskDO.setDialInterval(data.getDialInterval());
                }
                cmAcsTaskMapper.insert(acsTaskDO);
                //外显号码关联信息
                List<CmAcsTaskOutNumberDO> cmacsTaskOutNumberDOList = new ArrayList<>();
                data.getOutnumberIdList().forEach(item -> {
                    cmacsTaskOutNumberDOList.add(CmAcsTaskOutNumberDO.builder().acsTask(acsTaskDO.getId()).cmOutnumber(item).ctime(now).build());
                });
                cmAcsTaskOutNumberMapper.insertBatch(cmacsTaskOutNumberDOList);
                List<CmAcsTimehourDO> timehourDOList = new ArrayList<>();
                //时间区间
                if (ObjectUtil.isNotEmpty(data.getStartTime1()) && ObjectUtil.isNotEmpty(data.getEndTime1())) {
                    timehourDOList.add(CmAcsTimehourDO.builder().acsTask(acsTaskDO.getId()).ctime(now).starttime(data.getStartTime1()).endtime(data.getEndTime1()).build());
                }
                if (ObjectUtil.isNotEmpty(data.getStartTime2()) && ObjectUtil.isNotEmpty(data.getEndTime2())) {
                    timehourDOList.add(CmAcsTimehourDO.builder().acsTask(acsTaskDO.getId()).ctime(now).starttime(data.getStartTime2()).endtime(data.getEndTime2()).build());
                }
                if (ObjectUtil.isNotEmpty(data.getStartTime3()) && ObjectUtil.isNotEmpty(data.getEndTime3())) {
                    timehourDOList.add(CmAcsTimehourDO.builder().acsTask(acsTaskDO.getId()).ctime(now).starttime(data.getStartTime2()).endtime(data.getEndTime2()).build());
                }
                //默认呼叫区间为全天
                if (timehourDOList.isEmpty()) {
                    timehourDOList.add(CmAcsTimehourDO.builder().acsTask(acsTaskDO.getId()).ctime(now).starttime("00:00:00").endtime("23:59:59").build());
                }
                cmAcsTimehourMapper.insertBatch(timehourDOList);

                //呼叫名单
                this.appendTaskNumber(acsTaskDO.getId().toString(), data.getCalleeList());
                taskId = acsTaskDO.getId().toString();
            } catch (Exception e) {
                log.info("error:{}", e.getMessage());
                e.printStackTrace();
                throw e;
            }
            return taskId;
        });
    }


    @Override
    public void updateBatchTask(CreateCallTaskDTO data) {
        TenantUtils.execute(this.cmTenantId, () -> {
            LocalDateTime now = LocalDateTime.now();
            Long acsTaskId = NumberUtil.parseLong(data.getTaskId());
            CmAcsTaskDO acsTaskDO = CmAcsTaskDO.builder()
                    .id(acsTaskId)
                    .name(data.getName())
                    .acsFlow(getCmFlowId(data.getType()))
                    .maxnum(data.getMaxNum())
                    .maxDialtimes(1) //呼叫次数 也就是重呼次数+1
                    .status(CmTaskStatusEnum.STOP.getStatus()) //默认为暂停状态
                    .taskType(1101) //先固定为固定时间段每天循环：1101
                    .idleMultiple(data.getMultiplier())
                    .outdialType(CmTaskTypeEnum.getCmTypeByType(data.getType())) // 8000:转坐席，8001：转IVR 8002 智能外呼
                    .outdialValue(data.getThirdPartyGroupId())
                    .dialInterval(10)
                    .voiceName(data.getTimbreId()) //音色
                    .build();
            if (data.getAutoRecall().equals(0)) {
                //需要失败重呼
                acsTaskDO.setMaxDialtimes(data.getAutoRecall() + 1); //呼叫次数 也就是重呼次数+1
                acsTaskDO.setDialInterval(data.getDialInterval());
            }

            cmAcsTaskMapper.updateById(acsTaskDO);
            List<CmAcsTimehourDO> timehourDOList = new ArrayList<>();
            cmAcsTimehourMapper.delete(CmAcsTimehourDO::getAcsTask, acsTaskId);
            //外显号码关联信息
            List<CmAcsTaskOutNumberDO> cmacsTaskOutNumberDOList = new ArrayList<>();
            cmAcsTaskOutNumberMapper.delete(CmAcsTaskOutNumberDO::getAcsTask, acsTaskId);
            data.getOutnumberIdList().forEach(item -> {
                cmacsTaskOutNumberDOList.add(CmAcsTaskOutNumberDO.builder().acsTask(acsTaskDO.getId()).cmOutnumber(item).ctime(now).build());
            });
            cmAcsTaskOutNumberMapper.insertBatch(cmacsTaskOutNumberDOList);

            //时间区间
            if (ObjectUtil.isNotEmpty(data.getStartTime1()) && ObjectUtil.isNotEmpty(data.getEndTime1())) {
                timehourDOList.add(CmAcsTimehourDO.builder().acsTask(acsTaskDO.getId()).ctime(now).starttime(data.getStartTime1()).endtime(data.getEndTime1()).build());
            }
            if (ObjectUtil.isNotEmpty(data.getStartTime2()) && ObjectUtil.isNotEmpty(data.getEndTime2())) {
                timehourDOList.add(CmAcsTimehourDO.builder().acsTask(acsTaskDO.getId()).ctime(now).starttime(data.getStartTime2()).endtime(data.getEndTime2()).build());
            }
            if (ObjectUtil.isNotEmpty(data.getStartTime3()) && ObjectUtil.isNotEmpty(data.getEndTime3())) {
                timehourDOList.add(CmAcsTimehourDO.builder().acsTask(acsTaskDO.getId()).ctime(now).starttime(data.getStartTime2()).endtime(data.getEndTime2()).build());
            }
            //默认呼叫区间为全天
            if (timehourDOList.isEmpty()) {
                timehourDOList.add(CmAcsTimehourDO.builder().acsTask(acsTaskDO.getId()).ctime(now).starttime("00:00:00").endtime("23:59:59").build());
            }
            cmAcsTimehourMapper.insertBatch(timehourDOList);
        });
    }

    @Override
    public Integer appendTaskNumber(String thirdPartyTaskId, List<String> numberList) {
        return cmAcsTaskPhoneService.appendTaskNumber(thirdPartyTaskId, numberList);
    }

    /**
     * 呼叫任务控制
     */
    @Override
    public String callTaskControl(String taskId, String action) {
        return TenantUtils.execute(this.cmTenantId, () -> {
            Integer status;
            if (action.equals("start")) {
                status = CmTaskStatusEnum.STARTING.getStatus();
            } else {
                status = CmTaskStatusEnum.STOP.getStatus();
            }
            CmAcsTaskDO acsTaskDO = CmAcsTaskDO.builder().id(NumberUtil.parseLong(taskId)).status(status).build();
            cmAcsTaskMapper.updateById(acsTaskDO);
            return taskId;
        });
    }


    private Long getSkillId() {
        AtomicReference<Long> maxSkillId = new AtomicReference<>(1L);
        TenantUtils.executeIgnore(() -> {
            LambdaQueryWrapper<CmServiceSkillDO> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.select(CmServiceSkillDO::getSkillId).orderByDesc(CmServiceSkillDO::getSkillId).last("limit 1");
            CmServiceSkillDO maxSkill = cmServiceSkillMapper.selectOne(queryWrapper);
            if (Objects.nonNull(maxSkill)) {
                maxSkillId.set(maxSkill.getSkillId() + 1);
            }
        });

        return maxSkillId.get();
    }

    public Long getNotPushedMinId(Integer typeCode) {
        String redisKey = "not_pushed_min_id:" + typeCode;
        Long minId = NumberUtil.parseLong(stringRedisTemplate.opsForValue().get(redisKey));
        if (minId > 0) {
            return minId;
        }
        //查询较早的记录,防止漏掉之前未挂断的电话
        LocalDateTime agoTime = LocalDateTimeUtil.offset(LocalDateTime.now(), -3, ChronoUnit.HOURS);
        CmRecordPushDO recordPushed = cmRecordPushedMapper.selectOne(new LambdaQueryWrapper<CmRecordPushDO>()
                .eq(CmRecordPushDO::getType, typeCode)
                .lt(CmRecordPushDO::getCreateTime, agoTime)
                .orderByDesc(CmRecordPushDO::getId)
                .last("limit 1")
        );
        minId = Objects.isNull(recordPushed) ? 0 : recordPushed.getBindId();
        this.setNotPushedMinId(typeCode, minId);
        return minId;
    }

    public void setNotPushedMinId(Integer typeCode, Long minId) {
        String redisKey = "not_pushed_min_id:" + typeCode;
        stringRedisTemplate.opsForValue().set(redisKey, minId.toString(), 12, TimeUnit.HOURS);
    }


    /**
     * 点呼呼叫记录处理
     */
    public void handleCallOut(Consumer<CallNotifyDTO> Consumer, Integer limit) {
        AtomicBoolean exist = new AtomicBoolean(true);
        Integer typeCode = CmPushedTypeEnum.CALLOUT.getCode();
        AtomicReference<Long> minId = new AtomicReference<>(getNotPushedMinId(typeCode));
        String initDate = "2000-01-01 00:00:00";
        while (exist.get()) {
            //处理话单 查询上一时间段内未处理的
            TenantUtils.executeIgnore(() -> {
                List<CmCalloutDetailDO> calloutDetailDOList = cmCalloutDetailMapper.selectList(new MPJLambdaWrapperX<CmCalloutDetailDO>()
                        .isNotNull(CmCalloutDetailDO::getCallee)
                        .ne(CmCalloutDetailDO::getCallee, "")
                        .gt(CmCalloutDetailDO::getTalkEndTime, initDate)
                        .leftJoin(CmRecordPushDO.class, on -> on.eq(CmCalloutDetailDO::getId, CmRecordPushDO::getBindId)
                                .eq(CmRecordPushDO::getType, typeCode)
                        )
                        .isNull(CmRecordPushDO::getBindId)
                        .gt(CmCalloutDetailDO::getId, minId) //大于等于最小id
                        .last("limit " + limit)
                );
                if (calloutDetailDOList.isEmpty()) {
                    exist.set(false);
                    return;
                }
                List<CmRecordFileDO> cmRecordFileDOList = cmRecordFileMapper.selectList(new LambdaQueryWrapper<CmRecordFileDO>()
                        .in(CmRecordFileDO::getCallId, convertSet(calloutDetailDOList, CmCalloutDetailDO::getCallId)));
                Map<String, CmRecordFileDO> cmRecordFileDOMap = CollectionUtils.convertMap(cmRecordFileDOList, CmRecordFileDO::getCallId);

                calloutDetailDOList.forEach(calloutDetail -> {
                    CallNotifyDTO callNotifyDTO = new CallNotifyDTO();
                    if (calloutDetail.getTalkTime() > 0) { //当前接通时长大于0 表示接通了
                        if (Objects.equals(calloutDetail.getAgentid(), "")) {
                            //没有agentid,漏接了
                            callNotifyDTO.setResultCode("leak");
                            callNotifyDTO.setResultType(CallResultTypeEnum.AGENT_BUSY.getValue());
                            callNotifyDTO.setResultMsg(CallResultTypeEnum.AGENT_BUSY.getMsg());
                        } else {
                            callNotifyDTO.setResultCode("success");
                            callNotifyDTO.setResultType(CallResultTypeEnum.SUCCESS.getValue());
                            callNotifyDTO.setResultMsg(CallResultTypeEnum.SUCCESS.getMsg());
                            //接通后才记录接通时间
                            callNotifyDTO.setAnswerTime(calloutDetail.getTalkStartTime());
                        }
                    } else {
                        callNotifyDTO.setResultCode("fail");
                        callNotifyDTO.setResultType(CallResultTypeEnum.FAIL.getValue());
                        callNotifyDTO.setResultMsg(CallResultTypeEnum.FAIL.getMsg());
                    }
                    CmRecordFileDO recordFileDO = cmRecordFileDOMap.get(calloutDetail.getCallId());
                    if (!Objects.isNull(recordFileDO)) {
                        callNotifyDTO.setRecordUrl(this.recordDomain + recordFileDO.getFilePath());
                    }
                    callNotifyDTO.setReqNo(calloutDetail.getCallId());
                    callNotifyDTO.setBatchTask(false);
                    callNotifyDTO.setCallee(calloutDetail.getCallee());
                    callNotifyDTO.setStaffNo(calloutDetail.getAgentid());
                    callNotifyDTO.setCallSecond(calloutDetail.getTalkTime());
                    callNotifyDTO.setCallMinute((int) Math.ceil((double) calloutDetail.getTalkTime() / 60));
                    callNotifyDTO.setStartTime(calloutDetail.getCreateTime());
                    callNotifyDTO.setEndTime(calloutDetail.getTalkEndTime());
                    callNotifyDTO.setUserData(calloutDetail.getCallId());
                    Consumer.accept(callNotifyDTO);
                    minId.set(calloutDetail.getId());
                    //标识已推送
                    cmRecordPushedMapper.insert(CmRecordPushDO.builder().type(typeCode).bindId(calloutDetail.getId()).createTime(LocalDateTime.now()).build());
                });
                //数量不足表示没有了
                exist.set(calloutDetailDOList.size() == limit);
            });
        }
    }


    /**
     * 外呼任务呼叫记录处理
     */
    public void handleAcsDialLog(Consumer<CallNotifyDTO> Consumer, Integer limit) {
        AtomicBoolean exist = new AtomicBoolean(true);
        Integer typeCode = CmPushedTypeEnum.ACS_DIALLOG.getCode();
        AtomicReference<Long> minId = new AtomicReference<>(getNotPushedMinId(typeCode));
        String initDate = "2000-01-01 00:00:00"; //cm的默认时间
        while (exist.get()) {
            //处理话单 查询上一时间段内未处理的
            TenantUtils.executeIgnore(() -> {
                List<CmAcsDiallogDO> acsDiallogDOList = cmAcsDiallogMapper.selectList(new MPJLambdaWrapperX<CmAcsDiallogDO>()
                        .leftJoin(CmRecordPushDO.class, on -> on.eq(CmAcsDiallogDO::getId, CmRecordPushDO::getBindId)
                                .eq(CmRecordPushDO::getType, typeCode)
                        )
                        .isNull(CmRecordPushDO::getBindId)
                        .gt(CmAcsDiallogDO::getId, minId.get()) //大于等于最小id
                        .gt(CmAcsDiallogDO::getEndTime, initDate) //结束时间大于默认时间,表示已结束通话
                        .last("limit " + limit)
                );
                if (acsDiallogDOList.isEmpty()) {
                    exist.set(false);
                    return;
                }
                List<CmAcsTaskPhoneDO> cmAcsTaskPhoneDOList = cmAcsTaskPhoneService.selectList(new LambdaQueryWrapper<CmAcsTaskPhoneDO>()
                        .in(CmAcsTaskPhoneDO::getId, convertSet(acsDiallogDOList, CmAcsDiallogDO::getAcsTaskPhone))
                );

                Map<Long, CmAcsTaskPhoneDO> cmAcsTaskPhoneDOMap = CollectionUtils.convertMap(cmAcsTaskPhoneDOList, CmAcsTaskPhoneDO::getId);

                List<CmRecordFileDO> cmRecordFileDOList = cmRecordFileMapper.selectList(new LambdaQueryWrapper<CmRecordFileDO>()
                        .in(CmRecordFileDO::getCallId, convertSet(acsDiallogDOList, CmAcsDiallogDO::getCallId)));
                Map<String, CmRecordFileDO> cmRecordFileDOMap = CollectionUtils.convertMap(cmRecordFileDOList, CmRecordFileDO::getCallId);
                //坐席接入记录

                acsDiallogDOList.forEach(acsDiallog -> {
                    try {
                        CmAcsTaskPhoneDO cmAcsTaskPhone = cmAcsTaskPhoneDOMap.get(acsDiallog.getAcsTaskPhone());
                        if (ObjectUtil.isEmpty(cmAcsTaskPhone)) {
                            //有的数据号码与任务绑定关系查询不到,直接跳过;
                            return;
                        }
                        String batchTaskId = cmAcsTaskPhone.getAcsTask().toString();
                        CallNotifyDTO callNotifyDTO = new CallNotifyDTO();
                        if (acsDiallog.getDuration() > 0) { //当前接通时长大于0 表示接通了
                            if (Objects.equals(acsDiallog.getAgentid(), "")) {
                                //没有agentid,漏接了
                                callNotifyDTO.setResultCode("leak");
                                callNotifyDTO.setResultType(CallResultTypeEnum.AGENT_BUSY.getValue());
                                callNotifyDTO.setResultMsg(CallResultTypeEnum.AGENT_BUSY.getMsg());
                            } else {
                                callNotifyDTO.setResultCode("success");
                                callNotifyDTO.setResultType(CallResultTypeEnum.SUCCESS.getValue());
                                callNotifyDTO.setResultMsg(CallResultTypeEnum.SUCCESS.getMsg());
                            }
                            //兼容ivr类型,一直传应答时间,判断类型非to_ivr再清空
                            callNotifyDTO.setAnswerTime(acsDiallog.getAnswerTime());

                        } else {
                            callNotifyDTO.setResultCode("fail");
                            callNotifyDTO.setResultType(CallResultTypeEnum.FAIL.getValue());
                            callNotifyDTO.setResultMsg(CallResultTypeEnum.FAIL.getMsg());
                        }
                        CmRecordFileDO recordFileDO = cmRecordFileDOMap.get(acsDiallog.getCallId());
                        if (!Objects.isNull(recordFileDO)) {
                            callNotifyDTO.setRecordUrl(this.recordDomain + recordFileDO.getFilePath());
                        }
                        callNotifyDTO.setReqNo(acsDiallog.getCallId());
                        callNotifyDTO.setBatchTask(true);
                        callNotifyDTO.setBatchTaskId(batchTaskId);
                        callNotifyDTO.setCallee(acsDiallog.getCalled());
                        callNotifyDTO.setStaffNo(acsDiallog.getAgentid());     //坐席号
                        callNotifyDTO.setCallSecond(acsDiallog.getDuration());
                        callNotifyDTO.setCallMinute((int) Math.ceil((double) acsDiallog.getDuration() / 60));
                        callNotifyDTO.setStartTime(acsDiallog.getCtime());
                        callNotifyDTO.setEndTime(acsDiallog.getEndTime());
                        callNotifyDTO.setUserData(acsDiallog.getCallId());

                        Consumer.accept(callNotifyDTO);
                        minId.set(acsDiallog.getId());

                        //标识已推送
                        cmRecordPushedMapper.insert(CmRecordPushDO.builder().type(typeCode).bindId(acsDiallog.getId()).createTime(LocalDateTime.now()).build());
                    } catch (Exception e) {
                        log.error("cm_handleAcsDialLog_error:{}", e.getMessage());
                        log.error("acsDiallog:{}", acsDiallog);
                        e.printStackTrace();
                    }
                });
                //数量不足表示没有了
                exist.set(acsDiallogDOList.size() == limit);

            });
            exist.set(false);
        }
    }

    public CmAcsTaskDO queryTaskInfo(String taskId) {
        return cmAcsTaskMapper.selectById(taskId);
    }

    /**
     * 获取分机登录信息
     *
     * @param number
     * @return
     */
    @Override
    public JSONObject getAccountInfo(String number) {
        return TenantUtils.execute(this.cmTenantId, () -> {
            CmSubscriberDO cmSubscriberDO = cmSubscriberMapper.selectOne(CmSubscriberDO::getUsername, number);
            Map<String, Object> res = new HashMap<>();
            res.put("username", cmSubscriberDO.getUsername());
            res.put("domain", cmSubscriberDO.getDomain());
            res.put("password", cmSubscriberDO.getPassword());
            res.put("ws", this.ws);
            res.put("webRTC", this.webrtc);
            return JSONUtil.parseObj(res);
        });
    }

    private Map<String, Object> makeBody(Map<String, Object> data) {
        return new HashMap<String, Object>() {{
            put("token", DigestUtil.md5Hex(apiKey + data.get("method")));
            put("data", data);
        }};
    }

    @Override
    public Boolean forceLogout(String agentId) {
        Map<String, Object> data = new HashMap<String, Object>();
        data.put("method", "ForceLogout");
        data.put("connId", "-2");
        data.put("agent_id", agentId);
        data.put("tenant_id", this.cmTenantId);
        Map<String, Object> body = this.makeBody(data);

        HttpRequest httpRequest = HttpUtil.createPost(this.url).body(JSONUtil.toJsonStr(body));
        HttpResponse httpResponse = httpRequest.execute();
        log.info("forceLogout:{}", httpResponse.body());

        if (httpResponse.isOk()) {
            JSONObject resp = JSONUtil.parseObj(httpResponse.body());
            if (ObjUtil.equals(0, resp.get("code"))) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Boolean forceNotReady(String agentId) {
        Map<String, Object> data = new HashMap<String, Object>();
        data.put("method", "ForceNotReady");
        data.put("connId", "1");
        data.put("agent_id", agentId);
        data.put("tenant_id", this.cmTenantId);
        Map<String, Object> body = this.makeBody(data);
        HttpRequest httpRequest = HttpUtil.createPost(this.url).body(JSONUtil.toJsonStr(body));
        HttpResponse httpResponse = httpRequest.execute();

        log.info("body:{}", body);
        log.info("ForceNotReady:{}", httpResponse.body());
        if (httpResponse.isOk()) {
            JSONObject resp = JSONUtil.parseObj(httpResponse.body());
            if (ObjUtil.equals(0, resp.get("code"))) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Boolean forceReady(String agentId) {
        Map<String, Object> data = new HashMap<String, Object>();
        data.put("method", "ForceReady");
        data.put("connId", "1");
        data.put("agent_id", agentId);
        data.put("tenant_id", this.cmTenantId);
        Map<String, Object> body = this.makeBody(data);
        HttpRequest httpRequest = HttpUtil.createPost(this.url).body(JSONUtil.toJsonStr(body));
        HttpResponse httpResponse = httpRequest.execute();

        if (httpResponse.isOk()) {
            JSONObject resp = JSONUtil.parseObj(httpResponse.body());
            if (ObjUtil.equals(0, resp.get("code"))) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Boolean forceSkillChange(String agentId, List<Map<String, Object>> skills) {
        Map<String, Object> data = new HashMap<String, Object>();
        data.put("method", "ForceSkillChange");
        data.put("agent_id", agentId);
        data.put("tenant_id", this.cmTenantId);
        data.put("skills", skills);
        Map<String, Object> body = this.makeBody(data);

        HttpRequest httpRequest = HttpUtil.createPost(this.url).body(JSONUtil.toJsonStr(body));
        HttpResponse httpResponse = httpRequest.execute();
        if (httpResponse.isOk()) {
            JSONObject resp = JSONUtil.parseObj(httpResponse.body());
            if (ObjUtil.equals(0, resp.get("code"))) {
                return true;
            }
        }
        return false;
    }

    private Integer getCmFlowId(Integer type) {
        Integer acsFlowId;

        switch (type) {
            case 1: //普通群呼
                acsFlowId = this.acsFlowId;
                break;
            case 2: //ivr流程
            default:
                acsFlowId = this.ivrFlowId;
        }
        return acsFlowId;
    }
}
